#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "defs.h"
#include "externs.h"
#include "protos.h"

int in_if;            /* set when we are in an .if statement */
int if_expr;        /* set when parsing an .if expression */
int if_level;        /* level of nested .if's */
int if_state[256];    /* status when entering the .if */
int if_flag[256];    /* .if/.else status */
int skip_lines;        /* set when lines must be skipped */
int continued_line;    /* set when a line is the continuation of another line */
struct t_opcode* last_op_ptr = NULL;

/* ----
 * assemble()
 * ----
 * translate source line to machine language
 */
void
assemble(void)
{
    struct t_line *ptr;
    char *buf = NULL;
    unsigned char c;
    int     flag;
    int     ip, i, j;        /* prlnbuf pointer */

    /* init variables */
    lablptr = NULL;
    continued_line = 0;
    data_loccnt = -1;
    data_size = 3;
    data_level = 1;

    /* macro definition */
    if (in_macro) {
        i = SFIELD;
        if (colsym(&i))
            if (prlnbuf[i] == ':')
                i++;
        while (isspace(prlnbuf[i]))
            i++;
        if (pass == LAST_PASS)
            println();
        if (oplook(&i) >= 0) {
            if (opflg == PSEUDO) {
                if (opval == P_MACRO) {
                    error("Can not nest macro definitions!");
                    return;
                }
                if (opval == P_ENDM) {
                    if (!check_eol(&i))
                        return;
                    in_macro = 0;
                    return;
                }
            }
        }
        if (pass == FIRST_PASS) {
            ptr = (void *)mem_alloc(sizeof(struct t_line));
            buf = (void *)mem_alloc(strlen(&prlnbuf[SFIELD]) + 1);
            if ((ptr == NULL) || (buf == NULL)) {        
                error("Out of memory!");
                return;
            }
            strcpy(buf, &prlnbuf[SFIELD]);
            ptr->next = NULL;
            ptr->data = buf;
            if (mlptr)
                mlptr->next = ptr;
            else
                mptr->line = ptr;
            mlptr = ptr;
        }
        return;
    }

    /* IF/ELSE section;
     * check for a '.else' or '.endif'
     * to toggle state
     */
    if (in_if) {
        i = SFIELD;
        while (isspace(prlnbuf[i]))
            i++;
        if (oplook(&i) >= 0) {
            if (opflg == PSEUDO) {
                switch (opval) {
                case P_IF:            // .if
                case P_IFDEF:        // .ifdef
                case P_IFNDEF:        // .ifndef
                    if (skip_lines) {
                        if_level++;
                        if_state[if_level] = 0;
                    }
                    break;

                case P_ELSE:        // .else
                    if (!check_eol(&i))
                        return;
                    if (if_state[if_level]) {
                        skip_lines = !if_flag[if_level];
                        if (pass == LAST_PASS)
                            println();
                    }
                    return;

                case P_ENDIF:        // .endif
                    if (!check_eol(&i))
                        return;
                    if (if_state[if_level] && (pass == LAST_PASS))
                        println();
                    skip_lines = !if_state[if_level];
                    if_level--;
                    if (if_level == 0)
                        in_if = 0;
                    return;
                }
            }
        }
    }

    if (skip_lines)
        return;

    /* comment line */
    c = prlnbuf[SFIELD];
    if (c == ';' || c == '*' || c == '\0') {
//        if (c == '\0')
            lastlabl = NULL;
        if (pass == LAST_PASS)
            println();
        return;
    }

    /* search for a label */
    i = SFIELD;
    j = 0;
    while (isspace(prlnbuf[i])) {
        i++;
    }

    {
        int nTemp = i;
        while ('\0' != prlnbuf[nTemp]) {

            unsigned char ch = prlnbuf[nTemp];
            if ('\"' == ch)
            {
                nTemp++;
                while ('\0' != prlnbuf[nTemp])
                {
                    if ('\"' == prlnbuf[nTemp])
                    {
                        nTemp++;
                        break;
                    }
                    nTemp++;
                }
            }

            if (ch >= 0x80)
            {
                prlnbuf[nTemp] = char_map[ch];
            }

            nTemp++;
        }
    }

    for (;;) {
        c = prlnbuf[i + j];
        if (isdigit(c) && (j == 0))
            break;
        if (!isalnum(c) && (c != '_') && (c != '.'))
            break;
        j++;
    }
    if ((j == 0) || ((i != SFIELD) && (c != ':')))
        i = SFIELD;
    else {
        if (colsym(&i) != 0)
            if ((lablptr = stlook(1)) == NULL)
                return;
        if ((lablptr) && (prlnbuf[i] == ':'))
            i++;
    }

    /* skip spaces */
    while (isspace(prlnbuf[i]))
        i++;

    /* is it a macro? */
    ip = i;
    mptr = macro_look(&ip);
    if (mptr) {
        /* define label */
        labldef(loccnt, 1);

        /* output location counter */
        if (pass == LAST_PASS) {
            if (!asm_opt[OPT_MACRO])
                loadlc((page << bank_bits) + loccnt, 0);
        }

        /* get macro args */
        if (!macro_getargs(ip))
            return;

        /* output line */
        if (pass == LAST_PASS)
            println();

        /* ok */
        mcntmax++;
        mcounter = mcntmax;
        expand_macro = 1;
        mlptr = mptr->line;
        return;
    }

    /* an instruction then */
    ip = i;
    flag = oplook(&ip);
    if (flag < 0) {
        labldef(loccnt, 1);
        if ((flag == -1))
            error("Unknown instruction!");
        if ((flag == -2) && (pass == LAST_PASS)) {
            if (lablptr)
                loadlc(loccnt, 0);
            println();
        }
        lastlabl = NULL;
        return;
    }

    /* generate code */
    if (opflg == PSEUDO)
        do_pseudo(&ip);
    else if (labldef(loccnt, 1) == -1)
        return;
    else {
        /* output infos */
        data_loccnt = loccnt;

        /* check if we are in the CODE section */
        if (section != S_CODE)
            fatal_error("Instructions not allowed in this section!");

        /* generate code */
        opproc(&ip);

        /* reset last label pointer */
        lastlabl = NULL;
    }
}


/* ----
 * oplook()
 * ----
 * operation code table lookup
 * return symbol length if found
 * return -1 on syntax error
 * return -2 if no symbol
 */

int
oplook(int *idx)
{
    struct t_opcode *ptr;
    char name[16];
    unsigned char c;
    int flag;
    int hash;
    int    i;

    /* get instruction name */
    i = 0;
    opext = 0;
    flag = 0;
    hash = 0;

    for (;;) {
        c = toupper(prlnbuf[*idx]);
        if (c == ' ' || c == '\t' || c == '\0' || c == ';')
            break;
        if (!isalnum(c) && c != '.' && c != '*' && c != '=' && c != '_')
            return (-1);
        if (i == 15)
            return (-1);

        /* handle instruction extension */
        if (c == '.' && i) {
            if (flag)
                return (-1);
            flag = 1;
            (*idx)++;
            continue;
        }
        if (flag) {
            if (opext)
                return (-1);
            opext = c;
            (*idx)++;
            continue;
        }

        /* store char */
        name[i++] = c;
        hash += c;
        hash  = (hash << 3) + (hash >> 5) + c;
        (*idx)++;

        /* break if '=' directive */
        if (c == '=')
            break;
    }

    /* check extension */
    if (flag) {
        if ((opext != 'L') && (opext != 'H'))
            return (-1);
    }

    /* end name string */
    name[i] = '\0';

    /* return if no instruction */
    if (i == 0)
        return (-2);

    /* search the instruction in the hash table */
    ptr = inst_tbl[hash & 0xFF];
    last_op_ptr = ptr;

    while (ptr) {
        if (!strcmp(name, ptr->name)) {
            opproc = ptr->proc;
            opflg  = ptr->flag;
            opval  = ptr->value;
            optype = ptr->type_idx;

            if (opext) {
                last_op_ptr = NULL;

                /* no extension for pseudos */
                if (opflg == PSEUDO)
                    return (-1);
                /* extension valid only for these addressing modes */
                if (!(opflg & (IMM|ZP|ZP_X|ZP_IND_Y|ABS|ABS_X|ABS_Y)))
                    return (-1);
            }

            last_op_ptr = ptr;
            return (i);            
        }
        ptr = ptr->next;
    }

    last_op_ptr = NULL;
    /* didn't find this instruction */
    return (-1);
}

t_opcode* get_last_opcode()
{
    return last_op_ptr;
}

/* ----
 * addinst()
 * ----
 * add a list of instructions to the instruction
 * hash table
 */

void
addinst(struct t_opcode *optbl)
{
    int hash;
    int len;
    int i;
    char *ptr;
    char  c;

    if (optbl == NULL)
        return;

    /* parse list */
    while (optbl->name) {
        /* calculate instruction hash value */
        hash = 0;
        len  = strlen(optbl->name);
        ptr  = optbl->name;

        for (i = 0; i < len; i++) {
            c = *ptr++;
            hash += c;
            hash  = (hash << 3) + (hash >> 5) + c;
        }

        hash &= 0xFF;
        
        /* insert the instruction in the hash table */
        optbl->next = inst_tbl[hash];
        inst_tbl[hash] = optbl;

        /* next instruction */
        optbl++;
    }
}


/* ----
 * check_eol()
 * ----
 * check the end of line for garbage
 */

int
check_eol(int *ip)
{
    while (isspace(prlnbuf[*ip]))
        (*ip)++;
    if (prlnbuf[*ip] == ';' || prlnbuf[*ip] == '\0')
        return (1);
    else {
        error("Syntax error!");
        return (0);
    }
}

/* .if pseudo */

void
do_if(int *ip)
{
    labldef(loccnt, 1);

    /* get expression */
    if_expr = 1;
    if (!evaluate(ip, ';')) {
        if_expr = 0;
        return;
    }
    if_expr = 0;

    /* check for '.if' stack overflow */
    if (if_level == 255) {
        fatal_error("Too many nested IF/ENDIF!");
        return;
    }
    in_if = 1;
    if_level++;
    if_state[if_level] = !skip_lines;
    if (!skip_lines)
         skip_lines = if_flag[if_level] = value ? 0 : 1;

    if (pass == LAST_PASS) {
        loadlc(value, 1);
        println();
    }
}

/* .else pseudo */

void
do_else(int *ip)
{
    if (!in_if)
        fatal_error("Unexpected ELSE!");
}

/* .endif pseudo */

void
do_endif(int *ip)
{
    if (!in_if)
        fatal_error("Unexpected ENDIF!");
}

/* .ifdef/.ifndef pseudo */

void
do_ifdef(int *ip)
{
    labldef(loccnt, 1);

    /* skip spaces */
    while (isspace(prlnbuf[*ip]))
        (*ip)++;

    /* get symbol */
    if (!colsym(ip)) {
        error("Syntax error!");
        return;
    }
    if (!check_eol(ip))
        return;
    lablptr = stlook(0);

    /* check for '.if' stack overflow */
    if (if_level == 255) {
        fatal_error("Too many nested IF/ENDIF!");
        return;
    }
    in_if = 1;
    if_level++;
    if_state[if_level] = !skip_lines;
    if (!skip_lines) {
        if (optype) {
            /* .ifdef */
            skip_lines = if_flag[if_level] = (lablptr == NULL) ? 1 : 0;
        }
        else {
            /* .ifndef */
            skip_lines = if_flag[if_level] = (lablptr == NULL) ? 0 : 1;
        }
    }

    if (pass == LAST_PASS) {
        loadlc(!skip_lines, 1);
        println();
    }
}

